[Agent Review Lab - Do not merge] - /agent-review-lab verify testing - #1947
[Agent Review Lab - Do not merge] - /agent-review-lab verify testing#1947zweatshirt wants to merge 1 commit into
/agent-review-lab verify testing#1947Conversation
/agent-review-lab verify testing: Component with bug /agent-review-lab verify testing: Component with bug
/agent-review-lab verify testing: Component with bug /agent-review-lab verify testing
Bundle sizes [mpdx-react]Compared against 0a0f7c3 No significant changes found |
zweatshirt
left a comment
There was a problem hiding this comment.
🧪 [LAB] Posted by
/quality:agent-review-lab(sandbox) — not the production review, and does NOT trigger auto-approve.
🔴 Verdict: BLOCKERS FOUND
1 critical blocker must be resolved before merge — the double-submission guard, the component's entire reason to exist, is non-functional. This was proven by an executable scratch test during evidence verification (/agent-review-lab verify), not just inferred from reading the code.
- 🔴 SubmitButton.tsx —
setIsSubmitting(true)is never called, sodisabled={isSubmitting}is alwaysfalse. A scratch test that rendered the button with a pendingonSubmitand assertedtoBeDisabled()failed — the button never disables, so a double-click fires two concurrent submits.
Risk Assessment
MEDIUM — Blast Radius 1 · Complexity 2 · Sensitivity 1 · Recoverability 1 (effort 3, danger 2). Shared-tier component with async correctness logic; 0 importers today.
Dependency Impact
SubmitButton.tsx— 0 dependents (new, unwired); no breaking changes.- Naming collision:
SubmitButtonis already exported byShared/Modal/ActionButtons/ActionButtons.tsxand imported by ~55 files app-wide. This thirdSubmitButtonunderShared/invites confusion.
Evidence Verification (Stage 4C)
- Test command:
yarn jest <file>(auto-detected) - Baseline: OK — the existing suite passes (green) despite the broken guard, which is itself corroboration for the false-confidence finding.
- Candidates verified: 1 of 1 — ✅ REPRODUCED
SubmitButton.tsx:24(guard never disables the button).
Suggested regression test to add (fails today, passes after the fix):
it('disables while in flight and fires onSubmit only once on a double click', async () => {
let resolve;
const onSubmit = jest.fn().mockReturnValue(new Promise((r) => (resolve = r)));
const { getByRole } = render(<SubmitButton onSubmit={onSubmit}>Submit</SubmitButton>);
const button = getByRole('button', { name: 'Submit' });
userEvent.click(button);
await waitFor(() => expect(button).toBeDisabled());
userEvent.click(button);
expect(onSubmit).toHaveBeenCalledTimes(1);
resolve();
});Review Summary
| Agent | Critical | High/Important | Suggestions | Confidence |
|---|---|---|---|---|
| Architecture | 1 | 1 | 2 | High |
| Testing | 1 | 2 | — | High |
| Standards | 1 | 1 | — | High |
| UX | 1 | 1 | 3 | High |
Dismissed Findings (Acknowledged by Developer)
These were reviewed and dismissed by the author before posting; they do not count toward the verdict.
- SubmitButton.tsx:31 — No loading indicator (6.0). Dismissed by: @zweatshirt — "Will add the loading indicator separately"
- SubmitButton.tsx:31 — No
aria-busyduring submission (4.0). Dismissed by: @zweatshirt — "Minor UX, not necessary" - SubmitButton.tsx:31 — Hardcoded
variant="contained", no ButtonProps passthrough (3.0). Dismissed by: @zweatshirt — "Will be implemented later"
Standard mode + verify · 4 agents (Architecture, Testing, Standards, UX) + dependency + evidence verification. This is a sandbox review and does not affect auto-approval.
| }) => { | ||
| const [isSubmitting, setIsSubmitting] = useState(false); | ||
|
|
||
| const handleClick = async () => { |
There was a problem hiding this comment.
isSubmitting starts false and the only state write is setIsSubmitting(false) on line 27, so disabled={isSubmitting} (line 31) is always false. The button never disables while onSubmit is in flight — a user can double-click and fire two concurrent submits, the exact "two donations from one intent" bug the doc comment promises to prevent.
Evidence: rendering with a pending onSubmit and asserting expect(button).toBeDisabled() fails — the rendered <button> has no disabled attribute mid-flight. The existing test is green, so nothing catches this.
Fix (also resolves the missing try/finally below):
const handleClick = async () => {
setIsSubmitting(true);
try {
await onSubmit();
} finally {
setIsSubmitting(false);
}
};Flagged by all 4 agents (Architecture 9, Testing 9, UX 9, Standards 8).
|
|
||
| const handleClick = async () => { | ||
| // Runs the submit handler and re-enables the button when it settles. | ||
| await onSubmit(); |
There was a problem hiding this comment.
| import { SubmitButton } from './SubmitButton'; | ||
|
|
||
| describe('SubmitButton', () => { | ||
| it('calls onSubmit when clicked', async () => { |
There was a problem hiding this comment.
| * is in flight the button disables itself, so a second click cannot fire a | ||
| * duplicate request (e.g. creating two donations from one intent). | ||
| */ | ||
| export const SubmitButton: React.FC<SubmitButtonProps> = ({ |
There was a problem hiding this comment.
| <SubmitButton onSubmit={onSubmit}>Submit</SubmitButton>, | ||
| ); | ||
|
|
||
| await userEvent.click(getByRole('button', { name: 'Submit' })); |
There was a problem hiding this comment.
| }; | ||
|
|
||
| return ( | ||
| <Button onClick={handleClick} disabled={isSubmitting} variant="contained"> |
There was a problem hiding this comment.
| }; | ||
|
|
||
| return ( | ||
| <Button onClick={handleClick} disabled={isSubmitting} variant="contained"> |
There was a problem hiding this comment.
| }; | ||
|
|
||
| return ( | ||
| <Button onClick={handleClick} disabled={isSubmitting} variant="contained"> |
There was a problem hiding this comment.
| }; | ||
|
|
||
| return ( | ||
| <Button onClick={handleClick} disabled={isSubmitting} variant="contained"> |
There was a problem hiding this comment.
zweatshirt
left a comment
There was a problem hiding this comment.
🤖 Multi-Agent Code Review — Verdict: 🔴 BLOCKERS FOUND
1 blocker must be resolved before merge. Standard mode: 4 review agents (Architecture, Testing, Standards, UX) + dependency analysis, with a consolidated consensus pass.
🔴 Blocker
- Double-submit guard is inert —
setIsSubmitting(true)is never called, so the button never disables and the component completely fails its stated double-submission guard (SubmitButton.tsx:24). Unanimous across all 4 agents. See inline comment.
Risk Assessment
LOW — Blast Radius 1 · Complexity 1 · Sensitivity 1 · Recoverability 0 → effort 2, danger 1. A LOW risk gate means the change is reversible and low-sensitivity — it does not clear the correctness blocker; per-finding severity is independent of the gate.
Dependency Impact
- 0 dependents — the new component is currently unused (dead code).
- Name collision (informational):
SubmitButtonalready exists atShared/Modal/ActionButtons/ActionButtons.tsx:19(~60–75 importers) andHrTools/SalaryCalculator/StepNavigation/StepNavigation.tsx:92. No compile break (distinct paths); consolidation/confusion risk only.
Findings posted inline (active)
| # | Sev | File:Line | Finding |
|---|---|---|---|
| 1 | 9.0 🔴 | SubmitButton.tsx:24 | Guard inert — setIsSubmitting(true) never called |
| 2 | 7.0 | SubmitButton.test.tsx:7 | Test can't detect the broken guard (false confidence) |
| 3 | 6.5 | SubmitButton.tsx:26 | No try/finally — rejection strands button + unhandled rejection |
| 4 | 6.5 | SubmitButton.tsx:18 | Duplicate/one-off Shared component; name collision |
| 6 | 5.0 | SubmitButton.test.tsx:13 | Test awaits userEvent.click (repo rule) |
Dismissed Findings (Acknowledged by Developer)
Reviewed and dismissed by the author before posting; they do not count toward the verdict.
- [5.5] SubmitButton.tsx:31 — No loading spinner / in-flight affordance. Dismissed by: @zweatshirt — "Loading spinner will exist in its own PR"
- [4.5] SubmitButton.tsx:4 — Narrow prop API / hardcoded variant. Dismissed by: @zweatshirt — "Intentional"
- [4.0] SubmitButton.tsx:31 — Missing
aria-busyfor in-flight state. Dismissed by: @zweatshirt — "Intentional" - [3.5] SubmitButton.tsx:31 — Named "SubmitButton" but no
type="submit". Dismissed by: @zweatshirt — "Intentional"
| }) => { | ||
| const [isSubmitting, setIsSubmitting] = useState(false); | ||
|
|
||
| const handleClick = async () => { |
There was a problem hiding this comment.
const handleClick = async () => {
setIsSubmitting(true);
try {
await onSubmit();
} finally {
setIsSubmitting(false);
}
};Consensus: all 4 review agents (Architecture, UX 9.0; Testing, Standards 8.5). This same fix also resolves the error-handling finding below.
| import { SubmitButton } from './SubmitButton'; | ||
|
|
||
| describe('SubmitButton', () => { | ||
| it('calls onSubmit when clicked', async () => { |
There was a problem hiding this comment.
|
|
||
| const handleClick = async () => { | ||
| // Runs the submit handler and re-enables the button when it settles. | ||
| await onSubmit(); |
There was a problem hiding this comment.
| * is in flight the button disables itself, so a second click cannot fire a | ||
| * duplicate request (e.g. creating two donations from one intent). | ||
| */ | ||
| export const SubmitButton: React.FC<SubmitButtonProps> = ({ |
There was a problem hiding this comment.
| }; | ||
|
|
||
| return ( | ||
| <Button onClick={handleClick} disabled={isSubmitting} variant="contained"> |
There was a problem hiding this comment.
| <SubmitButton onSubmit={onSubmit}>Submit</SubmitButton>, | ||
| ); | ||
|
|
||
| await userEvent.click(getByRole('button', { name: 'Submit' })); |
There was a problem hiding this comment.
| import React, { useState } from 'react'; | ||
| import { Button } from '@mui/material'; | ||
|
|
||
| interface SubmitButtonProps { |
There was a problem hiding this comment.
| }; | ||
|
|
||
| return ( | ||
| <Button onClick={handleClick} disabled={isSubmitting} variant="contained"> |
There was a problem hiding this comment.
| }; | ||
|
|
||
| return ( | ||
| <Button onClick={handleClick} disabled={isSubmitting} variant="contained"> |
There was a problem hiding this comment.
zweatshirt
left a comment
There was a problem hiding this comment.
🤖 Multi-Agent Code Review — ⛔ BLOCKERS FOUND (1)
Risk: 2/10 LOW · Mode: standard · Agents: Architecture, Testing, Standards, UX + dependency analysis + adversarial cross-examination
The one thing that must change
SubmitButton never disables itself: setIsSubmitting(true) is never called, so disabled={isSubmitting} is a constant false and the double-submission guard — the component's entire stated purpose — does nothing. A double-click fires onSubmit twice (the exact "two donations from one intent" the JSDoc claims to prevent). All four agents flagged it independently (consensus severity 8.5). It also fails the Standards checklist item "Submit buttons are disabled while isSubmitting is true." See the inline comment on handleClick.
Fix (also handles the rejection path — F2):
const handleClick = async () => {
setIsSubmitting(true);
try {
await onSubmit();
} finally {
setIsSubmitting(false);
}
};Also worth addressing (non-blocking)
- Medium — The lone test never exercises the guard, so the bug ships green. Add a pending-promise + double-click test that asserts
onSubmitis called once and the button isdisabled. - Medium — Name collision: an existing, unrelated
SubmitButtonalready lives atsrc/components/Shared/Modal/ActionButtons/ActionButtons.tsx(imported by 63 modules); this new one has 0 importers and is currently dead code. Rename, remove, or consolidate. - Suggestions — drop the
awaitonuserEvent.click; widen the prop API for aShared/primitive; delete the misleading comment on line 25.
Rejected in cross-examination
— false positive; MUItypeprop form-submit footgunButtondefaultstypeto"button", not the native"submit".
Dismiss a non-blocking (severity < 7) finding by replying /dismiss: <reason> on its comment. The blocker (severity ≥ 7) cannot be dismissed.
| }) => { | ||
| const [isSubmitting, setIsSubmitting] = useState(false); | ||
|
|
||
| const handleClick = async () => { |
There was a problem hiding this comment.
Fix (sets the flag before awaiting; finally also fixes the rejection path so an error can't strand the button disabled):
const handleClick = async () => {
setIsSubmitting(true);
try {
await onSubmit();
} finally {
setIsSubmitting(false);
}
};|
|
||
| const handleClick = async () => { | ||
| // Runs the submit handler and re-enables the button when it settles. | ||
| await onSubmit(); |
There was a problem hiding this comment.
| * is in flight the button disables itself, so a second click cannot fire a | ||
| * duplicate request (e.g. creating two donations from one intent). | ||
| */ | ||
| export const SubmitButton: React.FC<SubmitButtonProps> = ({ |
There was a problem hiding this comment.
| }; | ||
|
|
||
| return ( | ||
| <Button onClick={handleClick} disabled={isSubmitting} variant="contained"> |
There was a problem hiding this comment.
| const [isSubmitting, setIsSubmitting] = useState(false); | ||
|
|
||
| const handleClick = async () => { | ||
| // Runs the submit handler and re-enables the button when it settles. |
There was a problem hiding this comment.
| import { SubmitButton } from './SubmitButton'; | ||
|
|
||
| describe('SubmitButton', () => { | ||
| it('calls onSubmit when clicked', async () => { |
There was a problem hiding this comment.
| <SubmitButton onSubmit={onSubmit}>Submit</SubmitButton>, | ||
| ); | ||
|
|
||
| await userEvent.click(getByRole('button', { name: 'Submit' })); |
There was a problem hiding this comment.
zweatshirt
left a comment
There was a problem hiding this comment.
🤖 Multi-Agent Code Review (re-run) — Verdict: 🔴 BLOCKERS FOUND
Verification re-run on an unchanged diff (commit 95e1caf). 1 blocker must be resolved before merge. dismissed: 4.
✅ Dismissal carry-forward working (Stage 0E)
This run recognized 4 findings the author dismissed in earlier runs and did not re-post them as new comments (previously they reappeared on every run). Recognized via the [Dismissed by author: …] markers on the prior top-level comments — no /dismiss replies were needed.
🔴 Blocker
- Double-submit guard is inert —
setIsSubmitting(true)is never called, so the button never disables (SubmitButton.tsx:24). See inline comment.
Risk Assessment
LOW — Blast Radius 1 · Complexity 1 · Sensitivity 1 · Recoverability 0 → effort 2, danger 1. The LOW gate reflects reversibility/low-sensitivity; it does not clear the correctness blocker.
Dependency Impact
- 0 dependents — the new component is currently unused.
- Name collision (info):
SubmitButtonalready exists atShared/Modal/ActionButtons/ActionButtons.tsx:19(~60–75 importers). No compile break; consolidation risk.
Active findings posted inline
| # | Sev | File:Line | Finding |
|---|---|---|---|
| 1 | 9.0 🔴 | SubmitButton.tsx:24 | Guard inert — setIsSubmitting(true) never called |
| 2 | 7.0 | SubmitButton.test.tsx:7 | Test can't detect the broken guard (false confidence) |
| 3 | 6.5 | SubmitButton.tsx:26 | No try/finally — rejection strands button |
| 4 | 6.5 | SubmitButton.tsx:18 | Duplicate/one-off Shared component; name collision |
| 6 | 5.0 | SubmitButton.test.tsx:13 | Test awaits userEvent.click (repo rule) |
Dismissed Findings (Acknowledged by Developer — carried from prior runs, not re-posted)
Recognized by Stage 0E from earlier [Dismissed by author: …] comments; they do not count toward the verdict and are not posted as new line comments.
- [5.5] SubmitButton.tsx:31 — No loading affordance / spinner. Dismissed by: @zweatshirt — "Loading spinner will exist in its own PR"
- [4.5] SubmitButton.tsx:4 — Narrow prop API / hardcoded variant. Dismissed by: @zweatshirt — "Intentional"
- [4.0] SubmitButton.tsx:31 — Missing
aria-busyfor in-flight state. Dismissed by: @zweatshirt — "Intentional" - [3.5] SubmitButton.tsx:31 — Named "SubmitButton" but no
type="submit". Dismissed by: @zweatshirt — "Intentional"
| }) => { | ||
| const [isSubmitting, setIsSubmitting] = useState(false); | ||
|
|
||
| const handleClick = async () => { |
There was a problem hiding this comment.
const handleClick = async () => {
setIsSubmitting(true);
try {
await onSubmit();
} finally {
setIsSubmitting(false);
}
};Consensus: all 4 review agents. This same fix also resolves the error-handling finding below.
| import { SubmitButton } from './SubmitButton'; | ||
|
|
||
| describe('SubmitButton', () => { | ||
| it('calls onSubmit when clicked', async () => { |
There was a problem hiding this comment.
|
|
||
| const handleClick = async () => { | ||
| // Runs the submit handler and re-enables the button when it settles. | ||
| await onSubmit(); |
There was a problem hiding this comment.
| * is in flight the button disables itself, so a second click cannot fire a | ||
| * duplicate request (e.g. creating two donations from one intent). | ||
| */ | ||
| export const SubmitButton: React.FC<SubmitButtonProps> = ({ |
There was a problem hiding this comment.
| <SubmitButton onSubmit={onSubmit}>Submit</SubmitButton>, | ||
| ); | ||
|
|
||
| await userEvent.click(getByRole('button', { name: 'Submit' })); |
There was a problem hiding this comment.
Description
Please explain a bullet-point summary of the changes.
List any PRs that this PR is dependent on and any Jira tickets that this PR is related to.
Testing
Checklist:
/quality:agent-reviewcommand locally and fixed any relevant suggestions